
  var common = require ('./common');

  var TreexyNode = module.exports = function () {
    this.parent = null;       // Родительский TreexyNode
    this.type = '';           // Тип тега: tag | text | root
    this.tagName = '';        // Имя тега
    this.attributes = {};     // Аттрибуты тега
    this.childs = [];         // Дочерние TreexyNode
    this.text = '';           // Текст в случае type=text
    this.initParams = [];     // Начальные параметры в случае вызова с %tag(…)
    
    this._initTreexyNode.apply (this, arguments);
  }
  
  TreexyNode._rxTag = /[\.\%\#]{1}[a-zA-Z]{1}[a-zA-Z0-9\.\#\_\-]*(\([^\)]*\)){0,1}(\[[^\]]*\]){0,1}([\s]|$)/g

  TreexyNode._requiredTagAttributes = {
    'area'      : { 'alt': '' },
    'bdo'       : { 'dir': 'ltr' },
    'form'      : { 'action': '.' },
    'html'      : { 'xmlns': 'http://www.w3.org/1999/xhtml' },
    'img'       : { 'alt': '', 'src': '' },
    'link'      : { 'rel': 'stylesheet', 'type': 'text/css' },
    'map'       : { 'name': '' },
    'meta'      : { 'content': '' },
    'optgroup'  : { 'label': '' },
    'param'     : { 'name': '' },
    'script'    : { 'type': 'text/javascript' },
    'style'     : { 'type': 'text/css' },
    'textarea'  : { 'cols': '3', 'rows': '40' }
  }

  TreexyNode._singleTags = ['area', 'base', 'br', 'col', 'hr', 'img', 'input', 'link', 'meta', 'param'];
  
  TreexyNode._tagInitParams = {
    'a'         : [ 'href', 'target', 'title' ],
    'img'       : [ 'src', 'alt' ],
    'input'     : [ 'type', 'name' ],
    'label'     : [ 'for' ],
    'link'      : [ 'href', 'rel', 'type' ],
    'script'    : [ 'src' ],
    'textarea'  : [ 'cols', 'rows' ],
    'form'      : [ 'action', 'method' ]
  }
  
  TreexyNode.prototype._pushNode = function (node, passToParent) {
    if ((this.type == 'tag' || this.type == 'root') && !this._isSingleTag ()) {
      this.childs.push (node);
      node.parent = this;
    } else {
      if (passToParent)
        this.parent._pushNode (node, true);
      else
        throw new Error ('Can\'t push ' + node.type + ' element to ' + this.type);
    }
  }
  
  TreexyNode.prototype._isSingleTag = function () {
    for (var i in TreexyNode._singleTags)
      if (this.tagName == TreexyNode._singleTags[i])
        return true;
    return false;
  }
  
  TreexyNode.prototype._initTreexyNode = function (parent, data, passToParent) {
    if (typeof parent == 'undefined') {
      this.type = 'root';
    } else {
      this.parent = parent;
      this.parent._pushNode (this, passToParent);
      switch (typeof data) {
        case 'string':
          this._fromString (data);
          break;
        default:
          throw new Error ('Bad type of \'data\' argument');
          break;
      }
    }
  }
  
  TreexyNode.prototype.toHtml = function () {
    if (this.type == 'text') {
      return this.text;
    }
    
    var ret = '';
    if (this.type == 'tag')
      ret += '<' + this.tagName;
    if (this.initParams.length > 0 && this.tagName in TreexyNode._tagInitParams) {
      for (var i = 0, l = this.initParams.length; i < l; i++) {
        if (TreexyNode._tagInitParams[this.tagName].length <= i)
          break;
        this.attributes[TreexyNode._tagInitParams[this.tagName][i]] = this.initParams[i];
      }
    }
    if (this.tagName in TreexyNode._requiredTagAttributes) {
      for (var i in TreexyNode._requiredTagAttributes[this.tagName]) {
        var requiredAttrValue = TreexyNode._requiredTagAttributes[this.tagName][i];
        if (!(i in this.attributes))
          this.attributes[i] = requiredAttrValue;
      }
    }
    var attributesNamesAndValues = [];
    for (var i in this.attributes)
      attributesNamesAndValues.push (i + '="' + this.attributes[i] + '"');
    var attributesString = attributesNamesAndValues.join (' ');
    if (attributesNamesAndValues.length > 0)
      ret += ' ' + attributesString;
    if (this.type == 'tag') {
      if (this._isSingleTag ())
        ret += '/>';
      else
        ret += '>';
    }
    if (this.type == 'root' || this.type == 'tag') {
      if (!this._isSingleTag ()) {
        var lastElWasText = false;
        for (var i in this.childs) {
          if (this.childs[i].type == 'text') {
            if (lastElWasText)
              ret += ' ';
            lastElWasText = true;
          }
          ret += this.childs[i].toHtml ();
        }
      }
    }
    if (this.type == 'tag' && !this._isSingleTag ())
      ret += '</' + this.tagName + '>';
    return ret;
  }
  
  TreexyNode.prototype._fromString = function (data) {
    var rxres;
    var found = [];
    while ((rxres = TreexyNode._rxTag.exec (data)) !== null)
      found.push (rxres);
    if (found.length > 0) {   // нашли тег(и)
      var elementsTrail = [];     // тут будут объекты типа TreexyNode, найденные в данной строке
      var stringOffsets = [];     // тут будут границы элементов (начала)
      stringOffsets.push (0);
      var lastIndex = 0;
      for (var i in found) {
        if (found[i]['index'] != lastIndex) { // если элемент-тег - первый, в stringOffsets добавлять не будем (уже есть)
          stringOffsets.push (found[i]['index']);
        }
        stringOffsets.push (found[i]['index'] + found[i][0].length);
        lastIndex = found[i]['index'] + found[i][0].length;
      }
      if (stringOffsets[stringOffsets.length - 1] != data.length)
        stringOffsets.push (data.length);
      for (var i = 0, l = stringOffsets.length - 1; i < l; i++) {
        elementsTrail.push (/*common.trim (*/data.substr (stringOffsets[i], stringOffsets[i + 1] - stringOffsets[i])/*)*/);
      }
      if (found[0]['index'] == 0) { // если первый элемент elementsTrail - тег, то оперируем с this
        var thisTagInfo = this._getTagInfo (elementsTrail[0]);
        for (var i in thisTagInfo)
          this[i] = thisTagInfo[i];
        this.type = 'tag';
        //this.parent._pushNode (this, true);
      } else { // а иначе - создаем новый
        //elementsTrail[0] = new TreexyNode (this.parent, elementsTrail[0]);
        //this.parent._pushNode (elementsTrail[0]);
        this,type = 'text';
        this.text = elementsTrail[0];
      }
      elementsTrail[0] = this;
      for (var i = 1, l = elementsTrail.length; i < l; i++) {
        elementsTrail[i] = new TreexyNode (elementsTrail[i - 1], elementsTrail[i], true);
        //elementsTrail[i - 1]._pushNode (elementsTrail[i], true);
      }
    } else {                  // не нашли ни одного тега
      this.type = 'text';
      this.text = data;
      //this.parent._pushNode (this);
    }
  }
  
  TreexyNode.prototype._getTagInfo = function (tag) {
    var rxres, info = {
      tagName: '',
      attributes: {},
      initParams: []
    };
    var rxresults = [];
    var rxId = /\#([a-zA-Z][a-zA-Z0-9\-\_]*)/
    var rxTag = /\%([a-zA-Z]+[1-6]{0,1})/
    var rxClass = /\.([a-zA-Z][a-zA-Z0-9\-\_]*)/g
    var rxAttributeNameValue = /([a-zA-Z][a-zA-Z0-9]*)\=\"([^\"]*)\"/g
    var rxInitParams = /\"(\\.|[^\"\\])*\"/g
    
    // [attr="value";attr="value"]
    var bs = tag.indexOf ('[');
    if (bs != -1) {
      var be = tag.indexOf (']', bs);
      if (be != -1) {
        var attrContent = tag.substr (bs + 1, be - bs -1);
        while ((rxres = rxAttributeNameValue.exec (attrContent)) !== null)
          rxresults.push (rxres);
        for (var i in rxresults) {
          info.attributes[rxresults[i][1].toLowerCase ()] = rxresults[i][2];
        }
      } else {
        throw new Error ('Syntax error: closing bracket not found in "' + tag + '"');
      }
      tag = tag.substr (0, bs);
    }
    
    // ("init1", "init2")
    var bs  = tag.indexOf ('(');
    if (bs != -1) {
      var be = tag.indexOf (')', bs);
      if (be != -1) {
        var initContent = tag.substr (bs + 1, be - bs - 1);
        rxresults = [];
        while ((rxres = rxInitParams.exec (initContent)) !== null)
          rxresults.push (rxres);
        for (var i in rxresults) {
          info.initParams.push (rxresults[i][0].replace (/^\"/, '').replace (/\"$/, '').replace (/\\\"/g, '"'));
        }
      } else {
        throw new Error ('Syntax error: closing bracket not found in "' + tag + '"');
      }
      tag = tag.substr (0, bs);
    }
    
    // %tag
    rxres = rxTag.exec (tag);
    if (rxres !== null)
      info.tagName = rxres[1].toLowerCase ();
    else
      info.tagName = 'div';
    
    // .class.onemore
    var classes = [];
    while ((rxres = rxClass.exec (tag)) !== null)
      classes.push (rxres[1]);
    if (classes.length > 0) {
      classes = classes.join (' ');
      if ('class' in info.attributes && info.attributes['class'] != '')
        info.attributes['class'] += ' ' + classes;
      else
        info.attributes['class'] = classes;
    }
    
    // #ID
    rxres = rxId.exec (tag);
    if (rxres !== null)
      info.attributes['id'] = rxres[1];
    
    return info;
  }
  
